package com.zhss.demo.register.client;

import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 服务注册中心的客户端缓存的一个服务注册表
 *
 */
public class CachedServiceRegistry {
	
	/**
	 * 服务注册表拉取间隔时间
	 */
	private static final Long SERVICE_REGISTRY_FETCH_INTERVAL = 30 * 1000L;

	/**
	 * 客户端缓存的服务注册表
	 */
	private AtomicStampedReference<Applications> applications;
	/**
	 * 负责定时拉取增量注册表到客户端进行缓存的后台线程
	 */
	private FetchDeltaRegistryWorker fetchDeltaRegistryWorker;
	/**
	 * RegisterClient
	 */
	private RegisterClient registerClient;
	/**
	 * http通信组件
	 */
	private HttpSender httpSender;
	/**
	 * 当前本地缓存的服务注册表的版本号
	 */
	private AtomicLong applicationsVersion = new AtomicLong(0L);
	/**
	 * 本地缓存注册表读写锁
	 */
	private ReentrantReadWriteLock applicationsLock =
			new ReentrantReadWriteLock();
	private ReentrantReadWriteLock.WriteLock applicationsWriteLock = applicationsLock.writeLock();
	private ReentrantReadWriteLock.ReadLock applicationsReadLock = applicationsLock.readLock();

	public CachedServiceRegistry(
			RegisterClient registerClient,
			HttpSender httpSender) {
		this.fetchDeltaRegistryWorker = new FetchDeltaRegistryWorker();
		this.registerClient = registerClient;
		this.httpSender = httpSender;
		this.applications = new AtomicStampedReference(new Applications(),0);
	}
	
	/**
	 * 初始化
	 */
	public void initialize() {
		//启动全量拉取注册表线程
		FetchFullRegistryWorker fetchFullRegistryWorker = new FetchFullRegistryWorker();
		fetchFullRegistryWorker.start();
		//启动增量拉取注册表线程
		this.fetchDeltaRegistryWorker.start();
	}
	
	/**
	 * 销毁这个组件
	 */
	public void destroy() {
		this.fetchDeltaRegistryWorker.interrupt();
	}
	
	/**
	 * 全量拉取注册表后台线程
	 * @author zhonghuashishan
	 *
	 */
	private class FetchFullRegistryWorker extends Thread {
		
		@Override
		public void run() {
			//在发起网络请求之前，先拿到当时的版本号
			//发起网络请求之后，注册表的版本号没被修改，才能去修改注册表
			fetchFullRegistry();
		}
		
	}

	/**
	 * 增量拉取注册表后台线程
	 * @author zhonghuashishan
	 *
	 */
	private class FetchDeltaRegistryWorker extends Thread {

		@Override
		public void run() {
			while(registerClient.isRunning()) {
				try {
					Thread.sleep(SERVICE_REGISTRY_FETCH_INTERVAL);
					Long  expectedApplicationsVersion = applicationsVersion.get();
					DeltaRegistry deltaRegistry = httpSender.fetchDeltaRegistry();
					if(applicationsVersion.compareAndSet(expectedApplicationsVersion,expectedApplicationsVersion + 1)){
						//一类是注册  一类是删除
						//如果是注册，判断这个服务实例是否在本地缓存的注册表
						//如果不在的话，就放到本地缓存注册表
						//如果是删除，服务实例存在就删除
						mergeDeltaRegistry(deltaRegistry);
						//再检查下，跟服务端的注册表的服务实例的数量是否一致
						//封装增量注册表对象，服务端增量注册表的数据和数量
						reconcileRegistry(deltaRegistry);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}

		private void mergeDeltaRegistry(DeltaRegistry deltaRegistry) {
			try{
				applicationsWriteLock.lock();
				Map<String,Map<String,ServiceInstance>>	registry =
						applications.getReference().getRegistry();
				Queue<RecentlyChangedServiceInstance> recentlyChangedQueue =
						deltaRegistry.getRecentlyChangedQueue();
				for (RecentlyChangedServiceInstance recentlyChangedItem : recentlyChangedQueue) {
					String serviceName = recentlyChangedItem.serviceInstance.getServiceName();
					String serviceInstanceId = recentlyChangedItem.serviceInstance.getServiceInstanceId();
					//如果是注册操作
					if(RecentlyChangedOperation.REGISTER.equals(
							recentlyChangedItem.serviceInstanceOperation)){
						Map<String,ServiceInstance>	serviceInstanceMap =
								registry.get(serviceName);
						if(serviceInstanceMap == null){
							serviceInstanceMap = new HashMap<String, ServiceInstance>();
							registry.put(serviceName,serviceInstanceMap);
						}

						ServiceInstance serviceInstance = serviceInstanceMap.get(serviceInstanceId);
						if(serviceInstance == null){
							serviceInstanceMap.put(serviceInstanceId,recentlyChangedItem.serviceInstance);
						}
					}
					//如果是删除操作
					else if(RecentlyChangedOperation.REMOVE.equals(
							recentlyChangedItem.serviceInstanceOperation)){
						Map<String,ServiceInstance>	serviceInstanceMap = registry.get(serviceName);
						if(serviceInstanceMap != null){
							serviceInstanceMap.remove(serviceInstanceId);
						}
					}
				}
			}finally {
				applicationsWriteLock.unlock();
			}
		}

		/**
		 * 校对调整注册表
		 * @param deltaRegistry
		 */
		private void reconcileRegistry(DeltaRegistry deltaRegistry){
			Map<String,Map<String,ServiceInstance>>	registry =
					applications.getReference().getRegistry();
			Long serviceSideTotalCount= deltaRegistry.getServiceInstanceTotalCount();
			Long clientSideTotalCount = 0L;
			for (Map<String, ServiceInstance> serviceInstanceMap : registry.values()) {
				clientSideTotalCount += serviceInstanceMap.size();
			}
			if(serviceSideTotalCount != clientSideTotalCount){
				//重新拉取全量注册表进行纠正
				//在发起网络请求之前，先拿到当时的版本号
				//发起网络请求之后，注册表的版本号没被修改，才能去修改注册表
				fetchFullRegistry();
			}
		}

	}

	/**
	 * 拉取全量注册表到本地
	 */
	private void fetchFullRegistry(){
		Long  expectedApplicationsVersion = applicationsVersion.get();
		Applications fetchedApplications = httpSender.fetchFullRegistry();
		if(applicationsVersion.compareAndSet(expectedApplicationsVersion,expectedApplicationsVersion + 1)) {
//			while (true) {
				Applications expectedApplications = applications.getReference();
				int expectedStamp = applications.getStamp();
				if (applications.compareAndSet(expectedApplications, fetchedApplications,
						expectedStamp, expectedStamp + 1)) {
					return;
				}
//			}
		}
	}
	/**
	 * 服务实例操作
	 */
	class RecentlyChangedOperation{
		public static final String REGISTER = "register";
		public static final String REMOVE = "remove";
	}

	/**
	 * 获取服务注册表
	 * @return
	 */
	public Applications getRegistry() {
		try {
			applicationsReadLock.lock();
			return applications.getReference();
		}finally {
			applicationsReadLock.unlock();
		}
	}

	/**
	 * 最近变化的服务实例
	 */
	public static class RecentlyChangedServiceInstance{
		/**
		 * 服务实例
		 */
		ServiceInstance serviceInstance;
		/**
		 * 发生变更的时间戳
		 */
		Long changedTimestamp;
		/**
		 * 变更操作
		 */
		String serviceInstanceOperation;

		public RecentlyChangedServiceInstance(
				ServiceInstance serviceInstance,
				Long changedTimestamp,
				String serviceInstanceOperation) {
			this.serviceInstance = serviceInstance;
			this.changedTimestamp = changedTimestamp;
			this.serviceInstanceOperation = serviceInstanceOperation;
		}

		@Override
		public String toString() {
			return "RecentlyChangedServiceInstance{" +
					"serviceInstance=" + serviceInstance +
					", changedTimestamp=" + changedTimestamp +
					", serviceInstanceOperation='" + serviceInstanceOperation + '\'' +
					'}';
		}
	}


}
